package org.goldenport.g3.channels.evernote

import scala.xml.{Node, Text, XML}
import scala.collection.mutable.Buffer
import scala.collection.JavaConversions.asScalaBuffer

import java.io.InputStream
import java.security.MessageDigest
import java.util.Date

import org.goldenport.g3._
import org.goldenport.g3.atom._
import org.goldenport.g3.messages._
import org.goldenport.g3.adapters.{G3FileStoreAdapter, G3FileStoreDriver}
import org.goldenport.wpath._

import org.apache.thrift.protocol.TBinaryProtocol
import org.apache.thrift.transport.THttpClient

import com.evernote.edam.`type`._
import com.evernote.edam.userstore._
import com.evernote.edam.notestore._

/**
 * @since   Jul   6, 2010
 * @version Apr. 10, 2011
 * @author  ASAMI, Tomoharu
 */
class EvernoteAdapter() extends G3FileStoreAdapter() {
  def create_Driver() = new EvernoteDriver
}

class EvernoteDriver extends G3FileStoreDriver {
  val EVERNOTE = "http://evernote.com/"
  private val consumer_key = "asami_tomoharu"
  private val consumer_secret = "e7e78b8098396661"
  private val user_store_url = "https://sandbox.evernote.com/edam/user"
  private val note_store_url_base = "http://sandbox.evernote.com/edam/note/"
  private var auth_token_option: Option[String] = None
  private var note_store_option: Option[NoteStore.Client] = None
  private lazy val auth_token = auth_token_option.get
  private lazy val note_store = note_store_option.get
  private lazy val note_books = note_store.listNotebooks(auth_token)
  private lazy val default_note_book = note_books.get(0)

  override def open() {
    val (username, password) = get_basic_auth(EVERNOTE)
    val userStoreTrans = new THttpClient(user_store_url)
    val userStoreProt = new TBinaryProtocol(userStoreTrans)
    val userStore = new UserStore.Client(userStoreProt, userStoreProt)
    val versionOk = userStore.checkVersion("g3",
        com.evernote.edam.userstore.Constants.EDAM_VERSION_MAJOR,
        com.evernote.edam.userstore.Constants.EDAM_VERSION_MINOR)
    if (!versionOk) {
      throw new IllegalArgumentException("XXX")
    }
    // Authenticate as a user & password
    val authResult = userStore.authenticate(username,
        password, consumer_key, consumer_secret)
    val user = authResult.getUser()
    auth_token_option = Some(authResult.getAuthenticationToken())
    // Set up the NoteStore
    val noteStoreUrl = note_store_url_base + user.getShardId()
    val noteStoreTrans = new THttpClient(noteStoreUrl)
    val noteStoreProt = new TBinaryProtocol(noteStoreTrans)
    note_store_option = Some(new NoteStore.Client(noteStoreProt, noteStoreProt))
  }

  protected def create_File(container: WPath, filename: String) = {
    create_note(container, filename, "")
  }

  protected def create_File(container: WPath, filename: String, in: InputStream) = {
    create_note(container, filename, in)
  }

  protected def create_File(container: WPath, filename: String, string: String) = {
    create_note(container, filename, string)
  }

  private def create_note(container: WPath, filename: String, content: AnyRef) = {
    val time = System.currentTimeMillis()
    val note = new Note()
    note.setTitle(filename)
    note.setCreated(time)
    note.setUpdated(time)
    note.setActive(true)
    note.setNotebookGuid(default_note_book.getGuid())
    note.setContent(make_note_content(content))
    val createdNote = note_store.createNote(auth_token, note)
    createdNote
  }

  private def make_note_content(content: AnyRef) = {
    "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
    "<!DOCTYPE en-note SYSTEM \"http://xml.evernote.com/pub/enml.dtd\">" +
    <en-note>{make_body(content)}</en-note>.toString
  }

  private def make_body(content: AnyRef): Node = {
    content match {
      case s: String => Text(s)
      case xml: Node => xml
      case in: InputStream => make_body(in)
    }
  }

  private def make_body(in: InputStream) = {
    error("XXX")
  }

  protected def read_Files(wpath: WPath): AnyRef = {
    def read_notebooks() = {
      def make_notebook_entry(notebook: Notebook) = {
        def make_atom_links = {
          Nil
        }

        def make_atom_g3_attrs = {
          Nil
        }

        AtomEntry(
          (AtomId(notebook.getGuid) ::
           AtomTitle(notebook.getName) ::
           AtomPublished(VDateTime(notebook.getServiceCreated)) ::
           AtomUpdated(VDateTime(notebook.getServiceUpdated)) ::
           (make_atom_links :::
            make_atom_g3_attrs)): _*
        )
      }

      AtomFeed(
        (for (notebook <- note_books) yield {
          make_notebook_entry(notebook)
        }).toList: _*
      )
    }

    def read_files(notebook: Notebook) = {
      val notes = find_notes(notebook)
      make_feed(notebook, notes)
    }

    def read_files_name(notebookname: String) = {
      read_files(find_notebook(notebookname))
    }

    def read_file_name(notebookname: String, notename: String) = {
      read_file(find_notebook(notebookname), notename)
    }

    def read_file(notebook: Notebook, notename: String) = {
      val notes = find_notes(notebook).filter { n =>
        n.getGuid == notename || n.getTitle == notename
      }
      if (notes.isEmpty) {
        throw new IllegalArgumentException("Not found = " + notebook.getName + ", " + notename)
      } else if (notes.length == 1) {
        make_entry(get_note(notes(0).getGuid))
      } else {
        make_feed(notebook, notes)
      }
    }

    if (wpath.isRoot) {
      wpath.length match {
        case 0 => read_notebooks()
        case 1 => read_files_name(wpath(0).name)
        case 2 => read_file_name(wpath(0).name, wpath(1).name)
        case _ => throw new IllegalArgumentException("XXX")
      }
    } else {
      wpath.length match {
        case 0 => read_files(default_note_book) 
        case 1 => read_file(default_note_book, wpath(0).name)
        case _ => throw new IllegalArgumentException("XXX")
      }
    }
  }

  private def find_notebook(notebookname: String): Notebook = {
    val notebooks = note_store.listNotebooks(auth_token)
    for (notebook <- notebooks) {
      if (notebook.getName() == notebookname ||
          notebook.getGuid() == notebookname) {
        return notebook
      }
    }
    throw new IllegalArgumentException("Notebook = " + notebookname)
  }

  private def find_notes(notebook: Notebook): List[Note] = {
    val filter = make_filter()
    val guid = notebook.getGuid
    filter.setNotebookGuid(guid)
    val noteList = note_store.findNotes(auth_token, filter, 0, 100)
    noteList.getNotes().toList
  }

  private def get_note(guid: String): Note = {
    note_store.getNote(auth_token, guid, true, true, false, false)
  }

  private def make_filter() = {
    val filter = new NoteFilter()
    filter.setOrder(NoteSortOrder.CREATED.getValue())
    filter.setAscending(true)
    filter
  }

  private def make_feed(notebook: Notebook, notes: List[Note]) = {
    AtomFeed(
      (
        AtomId(notebook.getGuid) ::
        AtomTitle(notebook.getName) ::
        AtomUpdated(VDateTime(notebook.getServiceUpdated)) ::
        (for (note <- notes) yield make_entry(note)).toList
      ): _*
    )
  }

  private def make_entry(note: Note) = {
    AtomEntry(
      (AtomId(note.getGuid) ::
       AtomTitle(note.getTitle) ::
       AtomPublished(VDateTime(note.getCreated)) ::
       AtomUpdated(VDateTime(note.getUpdated)) ::
       make_atom_content(note) ::
       (make_atom_links(note) :::
        make_atom_g3_attrs(note))): _*
    )
  }

  private def make_atom_content(note: Note) = {
    log_debug("Evernote content", note.getContent)
    note.getContent match {
      case s: String => {
        val xml = XML.loadString(s)
        new InlineOtherAtomContent(List(xml), "text/en-note") // XXX
      }
      case null => NullAtomContent
    }
  }

  // TODO blog List, Option, flatten
  private def make_atom_links(note: Note): List[AtomLink] = {
    Nil
  }

  private def make_atom_g3_attrs(note: Note): List[AtomExtensionElement] = {
    def make_attrs: List[AtomExtensionElement] = {
      val attrs = note.getAttributes
      List(
        make_extension("evernote-source", attrs.getSource),
        make_extension("evernote-sourceURL", attrs.getSourceURL),
        make_extension("evernote-sourceApplication", attrs.getSourceApplication),
        make_extension("evernote-author", attrs.getAuthor),
        make_extension("evernote-subjectDate", attrs.getSubjectDate),
        make_extension("evernote-latitude", attrs.getLatitude),
        make_extension("evernote-longitude", attrs.getLongitude),
        make_extension("evernote-altitude", attrs.getAltitude)
      ).flatten
    }

    // XXX
    def to_list[T](list: java.util.List[T]): List[T] = {
      if (list == null) Nil
      else list.toList
    }

    def make_rscs: List[AtomExtensionElement] = {
      var index = 0
      (for (rsc <- to_list(note.getResources)) yield {
        index += 1
        val attrs = rsc.getAttributes
        List(
          make_extension("evernote-data", rsc.getData),
          make_extension("evernote-mime", rsc.getMime),
          make_extension("evernote-width", rsc.getWidth),
          make_extension("evernote-height", rsc.getHeight),
          make_extension("evernote-sourceURL", attrs.getSourceURL),
          make_extension("evernote-timestamp", attrs.getTimestamp),
          make_extension("evernote-latitude", attrs.getLatitude),
          make_extension("evernote-longitude", attrs.getLongitude),
          make_extension("evernote-altitude", attrs.getAltitude),
          make_extension("evernote_cameraMake", attrs.getCameraMake),
          make_extension("evernote-cameraModel", attrs.getCameraModel),
          make_extension("evernote-fileName", attrs.getFileName),
          make_extension("evernote-attachment", attrs.isAttachment)
        ).flatten
      }).flatten
    }

    make_attrs ::: make_rscs
  }

  private def make_extension(name: String, value: Long) = {
    if (value == 0) None
    else Some(make_extension_element(name, value.toString))
  }

  private def make_extension(name: String, value: Double) = {
    if (value == 0) None
    else Some(make_extension_element(name, value.toString))
  }

  private def make_extension(name: String, value: Boolean) = {
    Some(make_extension_element(name, value.toString))
  }

  private def make_extension(name: String, value: AnyRef) = {
    if (value == null) None
    else Some(make_extension_element(name, value.toString))
  }

  private def make_extension_element(name: String, value: String) = {
    AtomExtensionElement("http://goldenport.org/xmlns/g3", "g3", name, value)
  }

  protected def write_File(wpath: WPath, id: String): AnyRef = {
    error("XXX")
  }

  protected def write_File(wpath: WPath, id: String, string: String): AnyRef = {
    error("XXX")
  }

  protected def write_File(wpath: WPath, id: String, in: InputStream): AnyRef = {
    error("XXX")
  }

  protected def remove_File(wpath: WPath): AnyRef = {
    error("XXX")
  }
}

@deprecated("rename")
class EvernoteSession {
//  private val consumer_key = "asamioffice"
//  private val consumer_secret = "383bf799413e92e1"
  private val consumer_key = "asami_tomoharu"
  private val consumer_secret = "e7e78b8098396661"
  private val user_store_url = "https://sandbox.evernote.com/edam/user"
  private val note_store_url_base = "http://sandbox.evernote.com/edam/note/"
  private val username = "demogon"
  private val password = "demogon00"
  private var auth_token: Option[String] = None

  val noteStore = open_store()
  val notebooks = noteStore.listNotebooks(auth_token.get)
  val defaultNotebook = notebooks.get(0)

  def open_store() = {
    val userStoreTrans = new THttpClient(user_store_url)
    val userStoreProt = new TBinaryProtocol(userStoreTrans)
    val userStore = new UserStore.Client(userStoreProt, userStoreProt)
    val versionOk = userStore.checkVersion("g3",
        com.evernote.edam.userstore.Constants.EDAM_VERSION_MAJOR,
        com.evernote.edam.userstore.Constants.EDAM_VERSION_MINOR)
    if (!versionOk) {
      throw new IllegalArgumentException("XXX")
    }
    // Authenticate as a user & password
    val authResult = userStore.authenticate(username,
        password, consumer_key, consumer_secret)
    val user = authResult.getUser()
    auth_token = Some(authResult.getAuthenticationToken())
    // Set up the NoteStore
    val noteStoreUrl = note_store_url_base + user.getShardId()
    val noteStoreTrans = new THttpClient(noteStoreUrl)
    val noteStoreProt = new TBinaryProtocol(noteStoreTrans)
    new NoteStore.Client(noteStoreProt, noteStoreProt)
  }

  def createNotes(path: String, content: AnyRef) = {
    content match {
      case record: Record => {
        
      }
      case records: RecordSet => {
        
      }
      case feed: AtomFeed => {
        
      }
      case entry: AtomEntry => {
        
      }
    }
  }

  def createNote(path: String, in: Option[InputStream]) = {
    val wpath = WPath(path)
    val name = wpath.leafName
    val time = System.currentTimeMillis()
    val note = new Note()
    note.setTitle(name)
    note.setCreated(time)
    note.setUpdated(time)
    note.setActive(true)
    note.setNotebookGuid(defaultNotebook.getGuid())
    val content = make_note(in)
    note.setContent(content.toString)
    val createdNote = noteStore.createNote(auth_token.get, note)
    createdNote
  }

  private def make_note(content: AnyRef) = {
    <en-note>{make_body(content)}</en-note>    
  }

  private def make_body(content: AnyRef): Node = {
    content match {
      case Some(in: InputStream) => make_body(in)
    }
  }

  private def make_body(in: InputStream) = {
    error("XXX")
  }

  def readNote(name: String) = {
    
  }

  def writeNote(name: String, content: AnyRef) = {
    
  }

  def writeNote(name: String, in: InputStream) = {
    
  }

  def removeNote(name: String) = {
    
  }
}

class EvernoteProvider extends WPathProvider {
  type NODE = EvernoteNode
  val root_Node = new EvernoteNode("")
}

class EvernoteNode(val name: String) extends WPathNode {
  type CONTENT = AnyRef
  type NODE = EvernoteNode

  override def create_Node(name: String) = new EvernoteNode(name)
}


trait EvernoteContext extends G3Module {
  def evernote(atom: Symbol): G3Channel = {
    add_channel_node(atom, context.createChannel(new EvernoteAdapter()))
  }
}

/*
    // Create a note containing a little text, plus the "enlogo.png" image
    Resource resource = new Resource();
    resource.setData(readFileAsData("enlogo.png"));
    resource.setMime("image/png");
    Note note = new Note();
    note.setTitle("Test note from EDAMDemo");
    note.setCreated(System.currentTimeMillis());
    note.setUpdated(System.currentTimeMillis());
    note.setActive(true);
    note.setNotebookGuid(defaultNotebook.getGuid());
    note.addToResources(resource);
    String hashHex = bytesToHex(resource.getData().getBodyHash());
    String content = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
        + "<!DOCTYPE en-note SYSTEM \"http://xml.evernote.com/pub/enml.dtd\">"
        + "<en-note>Here's the Evernote logo:<br/>"
        + "<en-media type=\"image/png\" hash=\"" + hashHex + "\"/>"
        + "</en-note>";
    note.setContent(content);
    Note createdNote = noteStore.createNote(authToken, note);
    System.out.println("Note created. GUID: " + createdNote.getGuid());
  }

  private static Data readFileAsData(String fileName) throws Exception {
    FileInputStream in = new FileInputStream(fileName);
    ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
    byte[] block = new byte[10240];
    int len;
    while ((len = in.read(block)) >= 0) {
      byteOut.write(block, 0, len);
    }
    in.close();
    byte[] body = byteOut.toByteArray();
    Data data = new Data();
    data.setSize(body.length);
    data.setBodyHash(MessageDigest.getInstance("MD5").digest(body));
    data.setBody(body);
    return data;
  }

  public static String bytesToHex(byte[] bytes) {
    StringBuilder sb = new StringBuilder();
    for (byte hashByte : bytes) {
      int intVal = 0xff & hashByte;
      if (intVal < 0x10) {
        sb.append('0');
      }
      sb.append(Integer.toHexString(intVal));
    }
    return sb.toString();
  }

}
*/
